/*
 * @(#)Sticky.java 7/26/2005
 *
 * Copyright 2002 - 2005 JIDE Software Inc. All rights reserved.
 */
package com.element.swing;

import com.element.util.UIUtil;

import javax.swing.*;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;

/**
 * <code>Sticky</code> is a helper class to make JList or JTree or JTable changing selection when mouse moves. To use
 * it, you simply call
 * <pre><code>
 * JList list = new JList();
 * new Sticky(list);
 * </code></pre>
 * or
 * <pre><code>
 * JTree tree = new JTree();
 * new Sticky(tree);
 * </code></pre>
 */
public class Sticky {
	private final JComponent _target;
	private static final StickyMouseMotionListener STICKY_MOUSE_MOTION_LISTENER = new StickyMouseMotionListener();

	public Sticky(JList<?> list) {
		_target = list;
		install();
	}

	public Sticky(JTree tree) {
		_target = tree;
		install();
	}

	public Sticky(JTable table) {
		_target = table;
		install();
	}

	/**
	 * Installs the listener to make the list or tree sticky. This method is called by constructor, so you don't need to
	 * call it unless you called {@link #uninstall()} to remove the listener.
	 */
	public void install() {
		_target.addMouseMotionListener(STICKY_MOUSE_MOTION_LISTENER);
	}

	/**
	 * Uninstalls the listener.
	 */
	public void uninstall() {
		_target.removeMouseMotionListener(STICKY_MOUSE_MOTION_LISTENER);
	}

	static private class StickyMouseMotionListener extends MouseMotionAdapter {
		@Override
		public void mouseMoved(MouseEvent e) {
			if (e.getSource() instanceof JList<?> list) {
				Point location = e.getPoint();
				Rectangle r = new Rectangle();
				list.computeVisibleRect(r);
				if (r.contains(location)) {
					updateListSelectionForEvent(e, list, false);
				}
			} else if (e.getSource() instanceof JTree tree) {
				Point location = e.getPoint();
				Rectangle r = new Rectangle();
				tree.computeVisibleRect(r);
				if (r.contains(location)) {
					updateTreeSelectionForEvent(e, tree, false);
				}
			} else if (e.getSource() instanceof JTable table) {
				Point location = e.getPoint();
				Rectangle r = new Rectangle();
				table.computeVisibleRect(r);
				if (r.contains(location)) {
					updateTableSelectionForEvent(e, table, false);
				}
			}
		}
	}

	/**
	 * A utility method used by the event listeners.  Given a mouse event, it changes the list selection to the list
	 * item below the mouse.
	 */
	private static void updateListSelectionForEvent(MouseEvent e, JList<?> list, boolean shouldScroll) {
		// XXX - only seems to be called from this class. shouldScroll flag is never true
		Point location = e.getPoint();
		if (list == null) return;
		int index = list.locationToIndex(location);
		if (index == -1) {
			if (location.y < 0)
				index = 0;
			else
				index = list.getModel() == null ? 0 : list.getModel().getSize() - 1;
		}
		if (list.getSelectedIndex() != index && index >= 0 && index < list.getModel().getSize()) {
			list.setSelectedIndex(index);
			if (shouldScroll) list.ensureIndexIsVisible(index);
		}
	}

	/**
	 * A utility method used by the event listeners.  Given a mouse event, it changes the list selection to the list
	 * item below the mouse.
	 */
	private static void updateTreeSelectionForEvent(MouseEvent anEvent, JTree tree, boolean shouldScroll) {
		Point location = anEvent.getPoint();
		if (tree == null)
			return;
		int index = tree.getRowForLocation(location.x, location.y);
		if (index != -1) {
			TreePath pathForRow = tree.getPathForRow(index);
			if (tree.getSelectionPath() != pathForRow) {
				tree.setSelectionRow(index);
				if (shouldScroll)
					tree.makeVisible(pathForRow);
			}
		}
	}

	/**
	 * A utility method used by the event listeners.  Given a mouse event, it changes the table selection to the table
	 * item below the mouse.
	 */
	private static void updateTableSelectionForEvent(MouseEvent anEvent, JTable table, boolean shouldScroll) {
		// XXX - only seems to be called from this class. shouldScroll flag is
		// never true
		Point location = anEvent.getPoint();
		if (table == null)
			return;
		int index = table.rowAtPoint(location);
		if (index == -1) {
			if (location.y < 0)
				index = 0;
			else
				index = table.getModel() == null ? 0 : table.getModel().getRowCount() - 1;
		}
		if (table.getSelectedRow() != index) {
			table.getSelectionModel().setSelectionInterval(index, index);
			if (shouldScroll)
				UIUtil.ensureRowVisible(table, index);
		}
	}
}
